在前一篇,我們了解 Opentelemetry node sdk 使用 HttpInstrumentation
來攔截 http 請求,透過 meter 來創建Histogram
物件,蒐集 duration 這個metric data;然後透過 PrometheusExporter
來創建 Http server,讓 Prometheus 服務來拉取資料。
接下來我們來一一實現吧!
在這次demo中,我們就簡單蒐集 http duraction 的總和指標。因此在 Histogram
物件中,主要就是一個sum
屬性來存放持續時間、以及和record
函數來記錄、更新 sum value:
class MockHistogram {
constructor(name, options) {
this.name = name;
this.sum = 0;
}
// 記錄數據
record(value) {
this.sum += value;
}
}
透過上一篇的分析,我們知道 Meter 是用來創建並管理多個 Histogram
。 在這次 demo 中,我們利用 createHistogram
創建新的 Histogram
物件,並用 _histograms
數組來存儲應用中的 Histogram
物件,同時透過 getHistogram
暴露出去
createHistogram(name, options) {
const histogram = new MockHistogram(name, options);
this._histograms.push(histogram);
return histogram;
}
getHistogram() {
return this._histograms;
}
跟 trace data 一樣,我們要來進行 http 攔截;不過不同於之前寫的 express middleware,這次我們嘗試直接使用 http 本身來進行服務端請求的攔截,所以發起請求和接收請求都會在 http instrumentation 中完成。
利用覆蓋 http.request = callback
來進行攔截`
http.request = function (...args) {
const req = originalHttpRequest.apply(this, args);
req.on('response', (res) => {
// 採集邏輯
});
return req;
};
我們可以覆寫掉 http.Server.prototype.emit = callback
來進行攔截:
const originalEmit = http.Server.prototype.emit;
http.Server.prototype.emit = function (event, ...args) {
if (event === 'request') {
// 採集邏輯
}
return originalEmit.apply(this, [event, ...args]);
};
首先,在Opentelemerty js中,計算時間會用到 process.hrtime()
,而不是 Date()
,主要是因為精確度的要求。Date 的精確度是到毫秒級(millisecond),而 hrtime 的精確度是到納秒級別(nanosecond)。所以,我們在計算 duration 的時候,流程會是這樣:
const startTime = process.hrtime()
const endTime = process.hrtime()
endTime
和startTime
來計算 duration然後把計算邏輯寫在攔截 http 的 callback 中
Histogram
在 HttpInstrumentation
中,我們可以先為 server http 和 client http 分別創建 Histogram
物件:
this._httpServerDurationHistogram = this.meter.createHistogram(
'http.server.duration',
{
description: 'Measures the duration of inbound HTTP requests.',
unit: 'ms',
},
);
this._httpClientDurationHistogram = this.meter.createHistogram(
'http.client.duration',
{
description: 'Measures the duration of outbound HTTP requests.',
unit: 'ms',
},
);
然後分別實現兩者對request/response 持續時間的採集邏輯 --- recordServerRequestDuration
和 recordClientRequestDuration
接著就可以在攔截的callback中使用了~ 完整程式碼可以參看 link
這個部分就比較簡單了,在初始化的時候就創建一個 web server,在/metrics
endpoint 暴露從 meter
蒐集並且格式化後的 metric data:
...
init() {
const server = http.createServer((req, res) => {
if (req.url === '/metrics') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
let text = '';
//格式化 metric data
res.end(text);
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(9469, () => {
console.log('Metrics available at http://localhost:9469/metrics');
});
}
...
我們在這個 Node.js demo 中新增了一個 API,它會向另一個 Python 服務發送一個請求:
app.get('/demo', async (req, res) => {
const data = await axios.get(`${PYTHON_SERVICE_URL}/api/demo-01`);
res.send('hello server');
});
運行應用後,發送一次 API 請求,然後打開 /metrics
endpoint,即可看到成功輸出的 HTTP 請求持續時間數據:
透過上述的 demo,我們自己實現了自己採集 HTTP 請求延遲的 metric data,並且模擬 Prometheus Exporter 那樣把 metric
data 暴露到 /metric
endpoint。
完整程式碼可以在此 Github repository中查看。